package org.etnaframework.module.web.api.autoconfigure;

import org.etnaframework.module.base.utils.JacksonUtils;
import org.etnaframework.module.web.api.IWebAccessLogCustomizer;
import org.etnaframework.module.web.api.IWebCommonCustomizer;
import org.etnaframework.module.web.api.WebCommonFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 通用Web API服务配置
 * <p>
 * 默认情况下只需要在pom.xml里面引用etna-boot-module-web-api就能直接使用，支持自定义配置模块：
 * <p>
 * 1、实现{@link IWebCommonCustomizer}，可自定义服务器的通用配置，包括异常处理规则（Filter/Servlet中出现的异常、错误页等）
 * 2、实现{@link IWebAccessLogCustomizer}，可以自定义记录HTTP请求日志（哪些日志要记录，记录的内容格式）
 *
 * @author jasyaf
 * @since 2023-12-30
 */
@AutoConfiguration(before = ErrorMvcAutoConfiguration.class) // 下面关于异常处理的功能需要覆盖ErrorMvcAutoConfiguration中的配置，故需要提前初始化
public class WebApiAutoConfiguration implements WebMvcConfigurer {

    /**
     * 配置通用配置、访问日志，这里都有默认的实现
     * 如果不满足业务需求想定制功能，实现对应的接口{@link IWebAccessLogCustomizer}和{@link IWebCommonCustomizer}即可
     */
    @Bean
    public WebCommonFilter webCommonFilter(@Autowired(required = false) IWebCommonCustomizer commonCustomizer, @Autowired(required = false) IWebAccessLogCustomizer accessLogCustomizer) {
        return new WebCommonFilter(commonCustomizer, accessLogCustomizer);
    }

    /**
     * 配置请求/返回的JSON处理器为{@link JacksonUtils}
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof StringHttpMessageConverter) {
                ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
            } else if (converter instanceof MappingJackson2HttpMessageConverter) {
                MappingJackson2HttpMessageConverter messageConverter = (MappingJackson2HttpMessageConverter) converter;
                messageConverter.setDefaultCharset(StandardCharsets.UTF_8);
                messageConverter.setObjectMapper(JacksonUtils.DEFAULT_JACKSON_MAPPER);
            }
        }
    }

    /**
     * SpringBoot通用异常处理
     * </p>
     * 注意：此配置无法处理Filter中抛出的异常，Filter中抛出的异常在{@link WebCommonFilter}里被额外捕获处理
     */
    @RestControllerAdvice
    static class WebExceptionAdvice {

        @Autowired
        private WebCommonFilter webCommonFilter;

        @ExceptionHandler(Throwable.class)
        void errorHandlerSpring(HttpServletRequest request, HttpServletResponse response, Throwable ex) {
            webCommonFilter.getCommonCustomizer().error(request, response, ex);
        }
    }

    /**
     * 通用错误页，用于处理404/500等情况
     * <p>
     * 什么情况下会到这里：
     * 1、SpringBoot在没有找到对应的业务Filter/Servlet来处理请求时，默认情况下最后会进入静态资源的处理，没有找到时就会进到这里(404)
     * 2、请求过程中抛出了异常，但{@link IWebCommonCustomizer#error(HttpServletRequest, HttpServletResponse, Throwable)}处理异常的过程中又抛出了异常，作为最后的兜底也会来到这里(500)
     * <p>
     * PS: 如果是纯API服务器，还有另外的处理方式，先在application.yml中配置
     * spring.web.resources.add-mappings: false
     * spring.mvc.throw-exception-if-no-handler-found: true
     * 这样在没有找到静态资源时，会抛出NoHandlerFoundException捕获并处理即可
     */
    @Controller
    @RequestMapping("${server.error.path:${error.path:/error}}") // 配置来自BasicErrorController，需保持同步才能正确跳转到错误页
    static class DefaultErrorController extends AbstractErrorController {

        @Autowired
        private WebCommonFilter webCommonFilter;

        public DefaultErrorController(ErrorAttributes errorAttributes) {
            super(errorAttributes);
        }

        @RequestMapping
        public void error(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpStatus status = getStatus(request);
            webCommonFilter.getCommonCustomizer().errorPage(request, response, status);
        }
    }
}
